﻿namespace Snipps.Infrastructure.Behaviors
{
    using System;
    using System.Windows;
    using System.Windows.Input;
    using System.Windows.Interactivity;
    using System.Windows.Interop;

    public class BorderlessWindowBehavior : Behavior<Window>
    {
        private const int WM_NCCALCSIZE = 0x83;

        private const int WM_NCPAINT = 0x85;

        private const int WM_NCACTIVATE = 0x86;

        private const int WM_NCHITTEST = 0x84;

        private const int HTLEFT = 0x0A;

        private const int HTRIGHT = 0x0B;

        private const int HTTOP = 0x0C;

        private const int HTTOPLEFT = 0x0D;

        private const int HTTOPRIGHT = 0x0E;

        private const int HTBOTTOM = 0x0F;

        private const int HTBOTTOMLEFT = 0x10;

        private const int HTBOTTOMRIGHT = 0x11;

        private IntPtr m_hwnd;

        private HwndSource m_hwndSource;

        protected override void OnAttached()
        {
            if (AssociatedObject.IsInitialized)
            {
                AddHwndHook();
            }
            else
            {
                AssociatedObject.SourceInitialized += AssociatedObject_SourceInitialized;
            }

            AssociatedObject.WindowStyle = WindowStyle.None;

            AssociatedObject.Loaded += (sender, args) =>
                {
                    var titleBar = AssociatedObject.FindName("PART_TitleBar") as UIElement;
                    if (titleBar != null)
                    {
                        titleBar.MouseDown += TitleBar_MouseDown;
                    }
                };

            base.OnAttached();
        }

        private void TitleBar_MouseDown(object sender, MouseButtonEventArgs e)
        {
            if (e.ChangedButton == MouseButton.Left)
            {
                AssociatedObject.DragMove();
            }
        }

        protected override void OnDetaching()
        {
            RemoveHwndHook();
            base.OnDetaching();
        }

        private void AddHwndHook()
        {
            m_hwndSource = PresentationSource.FromVisual(AssociatedObject) as HwndSource;
            if (m_hwndSource != null)
            {
                m_hwndSource.AddHook(HwndHook);
            }

            m_hwnd = new WindowInteropHelper(AssociatedObject).Handle;
        }

        private void RemoveHwndHook()
        {
            AssociatedObject.SourceInitialized -= AssociatedObject_SourceInitialized;
            m_hwndSource.RemoveHook(HwndHook);
        }

        private void AssociatedObject_SourceInitialized(object sender, EventArgs e)
        {
            AddHwndHook();
        }

        private IntPtr HwndHook(IntPtr hWnd, int message, IntPtr wParam, IntPtr lParam, ref bool handled)
        {
            var returnval = IntPtr.Zero;
            switch (message)
            {
                case WM_NCCALCSIZE:

                    /* Hides the border */
                    handled = true;
                    break;
                case WM_NCPAINT:
                    {
                        if (Environment.OSVersion.Version.Major >= 6)
                        {
                            var m = new MARGINS { bottomHeight = 1, leftWidth = 1, rightWidth = 1, topHeight = 1 };
                            NativeMethods.DwmExtendFrameIntoClientArea(m_hwnd, ref m);
                        }

                        handled = true;
                    }

                    break;
                case WM_NCACTIVATE:
                    {
                        /* As per http://msdn.microsoft.com/en-us/library/ms632633(VS.85).aspx , "-1" lParam
                         * "does not repaint the nonclient area to reflect the state change." */
                        returnval = NativeMethods.DefWindowProc(hWnd, message, wParam, new IntPtr(-1));
                        handled = true;
                    }

                    break;
                case WM_NCHITTEST:

                    // don't process the message on windows that can't be resized
                    var resizeMode = AssociatedObject.ResizeMode;
                    if (resizeMode == ResizeMode.CanMinimize || resizeMode == ResizeMode.NoResize
                        || AssociatedObject.WindowState == WindowState.Maximized)
                    {
                        break;
                    }

                    // get X & Y out of the message                   
                    var screenPoint = new Point((short)lParam, (short)(lParam.ToInt32() >> 16));

                    // var screenPoint = new Point(UnsafeNativeMethods.GET_X_LPARAM(lParam), UnsafeNativeMethods.GET_Y_LPARAM(lParam));

                    // convert to window coordinates
                    var windowPoint = AssociatedObject.PointFromScreen(screenPoint);
                    var windowSize = AssociatedObject.RenderSize;
                    var windowRect = new Rect(windowSize);
                    windowRect.Inflate(-6, -6);

                    // don't process the message if the mouse is outside the 6px resize border
                    if (windowRect.Contains(windowPoint))
                    {
                        break;
                    }

                    var windowHeight = (int)windowSize.Height;
                    var windowWidth = (int)windowSize.Width;

                    // create the rectangles where resize arrows are shown
                    var topLeft = new Rect(0, 0, 6, 6);
                    var top = new Rect(6, 0, windowWidth - 12, 6);
                    var topRight = new Rect(windowWidth - 6, 0, 6, 6);

                    var left = new Rect(0, 6, 6, windowHeight - 12);
                    var right = new Rect(windowWidth - 6, 6, 6, windowHeight - 12);

                    var bottomLeft = new Rect(0, windowHeight - 6, 6, 6);
                    var bottom = new Rect(6, windowHeight - 6, windowWidth - 12, 6);
                    var bottomRight = new Rect(windowWidth - 6, windowHeight - 6, 6, 6);

                    // check if the mouse is within one of the rectangles
                    if (topLeft.Contains(windowPoint))
                    {
                        returnval = (IntPtr)HTTOPLEFT;
                    }
                    else if (top.Contains(windowPoint))
                    {
                        returnval = (IntPtr)HTTOP;
                    }
                    else if (topRight.Contains(windowPoint))
                    {
                        returnval = (IntPtr)HTTOPRIGHT;
                    }
                    else if (left.Contains(windowPoint))
                    {
                        returnval = (IntPtr)HTLEFT;
                    }
                    else if (right.Contains(windowPoint))
                    {
                        returnval = (IntPtr)HTRIGHT;
                    }
                    else if (bottomLeft.Contains(windowPoint))
                    {
                        returnval = (IntPtr)HTBOTTOMLEFT;
                    }
                    else if (bottom.Contains(windowPoint))
                    {
                        returnval = (IntPtr)HTBOTTOM;
                    }
                    else if (bottomRight.Contains(windowPoint))
                    {
                        returnval = (IntPtr)HTBOTTOMRIGHT;
                    }

                    if (returnval != IntPtr.Zero)
                    {
                        handled = true;
                    }

                    break;
            }

            return returnval;
        }
    }
}